/**
 *****************************************************************************
 * @file    drv_flash_api.c
 *
 * @brief    -
 *
 * Copyright (C) RealMega 2019-2020
 *
 *****************************************************************************
*/

#include "co_types.h"
#include "hal_irq.h"
#include "hal_spi_flash.h"

#include "drv_flash_api.h"
#include "_reg_spiflash.h"

void drv_flash_wait_write_done(void)
{
    uint8_t     status = 1;

    /*retrieve status register's value of flash chip, S0 was BUSY bit.*/
    while(status & 0x01)
    {
        hal_spi_flash_flush_fifo(true, false);
        status = hal_spi_flash_read_status_register(FLASH_CMD_READ_STATUS_REG1);
    }
}

void drv_flash_page_program(uint32_t flash_addr, const uint8_t *data, uint32_t length)
{
    bool quad_mode = false;
    
    HAL_SYS_DISABLE_IRQ();
    hal_spi_flash_disable_read();
    hal_spi_flash_send_command(FLASH_CMD_DISABLE_QPI);


	hal_spi_flash_flush_fifo(false, true);
    hal_spi_flash_send_data(data, length, quad_mode);
    hal_spi_flash_send_command(FLASH_CMD_WRITE_ENABLE);
    hal_spi_flash_page_program(FLASH_CMD_PAGE_PROGRAM, flash_addr);

    drv_flash_wait_write_done();

    hal_spi_flash_enable_read();
    HAL_SYS_ENABLE_IRQ();
}


uint8_t drv_flash_unit_erase(uint8_t unit_type, uint32_t start_addr)
{
    uint8_t     cmd;
    uint32_t    block_size = 0;

    if(unit_type >= FLASH_ERASE_UNIT_RESERVED)
    {
        return CO_ERR_INVALID_PARAM;
    }

    if(unit_type == FLASH_ERASE_SECTOR)
    {
        block_size = 0x1000;
        cmd = FLASH_CMD_SECTOR_ERASE;
    }
    else if(unit_type == FLASH_ERASE_BLOCK32)
    {
        block_size = 0x8000;
        cmd = FLASH_CMD_BLOCK32_ERASE;
    }
    else if(unit_type == FLASH_ERASE_BLOCK64)
    {
        block_size = 0x10000;
        cmd = FLASH_CMD_BLOCK64_ERASE;
    }
    else
    {
        start_addr = 0;
        block_size = 0;
        cmd = FLASH_CMD_CHIP_ERASE;
    }

    start_addr &= HAL_SPI_FLASH_ADDR_MASK;

    // check block alignment and boundary
    if((start_addr + block_size) > FLASH_SIZE)
    {
        return CO_ERR_EXCEED_LIMITS;
    }

    if(start_addr & (block_size-1))
    {
        return CO_ERR_NOT_ALIGNED;
    }
    
    hal_spi_flash_disable_read();
    hal_spi_flash_send_command(FLASH_CMD_DISABLE_QPI);
    
    hal_spi_flash_send_command(FLASH_CMD_WRITE_ENABLE);
    hal_spi_flash_erase(cmd, start_addr);
    drv_flash_wait_write_done();

    hal_spi_flash_enable_read();
    
    return true;
}

uint8_t drv_flash_write(uint32_t start_addr, const uint8_t* data, uint32_t length)
{
    uint32_t size;
    uint32_t page_size;

    if( data == NULL )
    {
        return CO_ERR_INVALID_PARAM;
    }

    start_addr &= HAL_SPI_FLASH_ADDR_MASK;
    if(start_addr + length > FLASH_SIZE)
    {
        return CO_ERR_EXCEED_LIMITS;
    }
    
    page_size = 256;
    /*check if align with PAGE_SIZE*/
    if(start_addr & (page_size-1))
    {
        size = page_size - (start_addr & (page_size-1));
        if(size > length)
        {
            size = length;
        }
        drv_flash_page_program(start_addr, data, size);

        length      -= size;
        start_addr  += size;
        data        += size;
    }

    while(length > 0)
    {
        if(length > page_size)
        {
            size = page_size;
        }
        else
        {
            size = length;
        }
        
        drv_flash_page_program(start_addr, data, size);

        length      -= size;
        start_addr  += size;
        data        += size;
    }
    return CO_ERR_SUCCESS;
}

uint8_t drv_flash_open()
{
    return CO_ERR_SUCCESS;
}

